/*
 * Listener.java
 */
package rehab.host.conn;

import rehab.host.global.SensorTypes;
import rehab.host.global.PacketTypes;
import rehab.host.spotfinder.BitMap;
import rehab.host.gui.MainFrame;

import com.sun.spot.io.j2me.radiogram.*;
import com.sun.spot.peripheral.NoAckException;
import com.sun.spot.peripheral.TimeoutException;
import com.sun.spot.util.IEEEAddress;

import java.awt.Graphics;
import java.io.*;
import javax.microedition.io.*;
import java.util.Vector;
import rehab.host.global.SingleSPOT;
import rehab.host.gui.GraphView;

/**
 * @author krish
 */
public class Listener extends Thread implements PacketTypes, SensorTypes {

    private double zeroOffsets[][] = { { 465, 465, 465 }, { 465, 465, 465 } };
    private double gains[][]       = { { 186.2, 186.2, 186.2 }, { 62.07, 62.07, 62.07 } };
    private double restOffsets[][] = { { 465, 465, 465 + 186 }, { 465, 465, 465 + 62 } };  // w/SPOT sitting flat Z up

    // private boolean baseStationPresent = false;
    private RadiogramConnection myConn = null;
    private RadiogramConnection rcvConn = null;
    private Radiogram xdg = null;
    private boolean running = true;
    private boolean connected = false;
    private long spotAddress = 0;
//    private long timeStampOffset = -1;
//    private final int MAXSPOTS = 10;
//    private final int MAXSENSORS = 15;
//    private int scaleInUse = 2;
//    private int index = 0;
    private int packetcnt = 0;
    private boolean DEBUG = false;
    //private Vector<Byte> SensorFormat;
    //
    private Integer connected_port;
    //
    private Vector mySPOTLIST;  // Vector itself is synchronized
    // Shared Variable. Need synchronization
    private BitMap PORTBitMap;
    private Vector sensorFormat_Spot = null;
    private Vector sensorFormat_Gui = null;
    private int myIndex;
    private Vector sensorUpdates = new Vector();
//    private GraphView[] graphView = {null, null, null, null};
    private GraphView[][] graphView = null;

    //TODO: check if the database operations are thread safe
    public MainFrame myGUI = null;
    private Graphics g;

    /**Constructor
     *
     * @param port
     */
//    public Listener (String port) {
//        connected_port = port;
//        sensorFormat = new Vector();
//        init();
//    }
    /**
     * Send a request to the remote SPOT to start or stop sending accelerometer readings.
     *
     * @param sendIt true to start sending, false to stop
     * @param gView the GraphView display to pass the data to
     */
    public void doSendData(boolean sendIt) {

        if (sendIt) {

            announceStarting();
        } else {
            announceClosing();
        }
    //sendCmd(sendIt ? SEND_ACCEL_DATA_REQ : STOP_ACCEL_DATA_REQ);
    }

    /**Overloaded Constructor
     *
     * @param port
     * @param SPOTLIST
     * @param PORTBitMap 
     * @param GUI
     */

    public Listener(int port, Vector SPOTLIST, BitMap PORTBitMap, MainFrame GUI) {
        if(DEBUG) System.out.println("Listener: received port " + port + " from BaseStation");
        connected_port = new Integer(port);
        this.PORTBitMap = PORTBitMap;
        //synchronized(SPOTLIST){
        this.mySPOTLIST = SPOTLIST;

        this.myGUI = GUI;

    }

    private void findMyIndex() {
        boolean found = false;
        for (int i = 0; i < mySPOTLIST.size(); i++) {
            SingleSPOT s = (SingleSPOT) mySPOTLIST.get(i);
            if(DEBUG) System.out.println("Listener: Printing SPOTLIST PortNumbers" + s.getPort() + "");
            if (connected_port.equals(new Integer(s.getPort()))) {
                myIndex = i;
                found = true;
                break;
            }
        }
        if (found) {
            if(DEBUG) System.out.println(" Listneer: Found my index " + myIndex);
        } else {
            if(DEBUG) System.out.println("Listener: Cannot find the SPOT In the SPOTlist");
        }
    }
    /*this will get te sensor list from the gui and 
     *re-index them to new format recieved from free ranger
     */

    private void getSensorList() {
        if(DEBUG) System.out.println("Index of the spot in the list from the gui" + myIndex);
        sensorFormat_Gui = myGUI.spotReturnType[myIndex];
    }

    private void sendCmd(byte sensor, byte cmd) {
        if (myConn != null) {
            try {
                xdg.reset();
                xdg.writeByte(sensor);
                xdg.writeByte(cmd);
                myConn.send(xdg);
            } catch (NoAckException nex) {
                connected = false;
            // updateConnectionStatus(connected);
            } catch (IOException ex) {
                //ignore any other problems
            }
        } else {
            if(DEBUG) System.out.println("Listener: the connection is null");

        }
    }

    public void addSensors(Vector sensorList) {
        //TODO to add support sensorType+packetType
        for (int i = 0; i < sensorList.size(); i++) {
            Byte s = (Byte) sensorList.get(i);
            sendCmd(s.byteValue(), ADD_SENSOR);
        }

    }

    public void finishConfig() {
        //TODO
        sendCmd(GENERIC, FINISH_CONFIG_REQ);

    }

    /** wait for spot is used in the thread.
     */
    private void waitForSpot() {


        // RadiogramConnection rcvConn = null;
        DatagramConnection txConn = null;
        spotAddress = 0;
        try {
            rcvConn = (RadiogramConnection) Connector.open("radiogram://:" + connected_port);
            rcvConn.setTimeout(10000);             // timeout in 10 seconds
            Datagram dg = rcvConn.newDatagram(rcvConn.getMaximumLength());

            while (true) {
                try {
                    dg.reset();
                    if(DEBUG) System.out.println("Listener: waitforspot() receive");
                    myConn.receive(dg); // wait until we receive a request
                    byte recvByte = dg.readByte();
                    recvByte = dg.readByte();
                    if(DEBUG) System.out.println("Listener: waitforspot() received packet " + recvByte + " ");

                    if (recvByte == ACK_FOR_ASSIGNPORT_PACKET) {
                        // type of packet
                        String addr = dg.getAddress();
                        IEEEAddress ieeeAddr = new IEEEAddress(addr);
                        long macAddress = ieeeAddr.asLong();
                        if(DEBUG) System.out.println("Listener: Received request from: " + ieeeAddr.asDottedHex());
                        spotAddress = macAddress;
                        //if(DEBUG) System.out.println("Listener: Received request from: " + ieeeAddr.asDottedHex());
                        /*Datagram rdg = rcvConn.newDatagram(10);
                        rdg.reset();
                        rdg.setAddress(dg);
                        rdg.writeByte(DISPLAY_SERVER_AVAIL_REPLY);  // packet type
                        rdg.writeLong(macAddress); // requestor's ID
                        rcvConn.send(rdg); // broadcast it
                         */
                        break;
                    }
                } catch (TimeoutException ex) {
                    announceStarting();
                }
            }
        } catch (Exception ex) {
            if(DEBUG) System.out.println("Error waiting for remote Spot: " + ex.toString());
            ex.printStackTrace();
        } finally {
            try {
                if (myConn != null) {
                    myConn.close();
                }
                if (txConn != null) {
                    txConn.close();
                }
            } catch (IOException ex) {
                // ignore
            }
        }
    }

    /**
     * Broadcast that the host display server is (re)starting.
     */
    private void announceStarting() {
        DatagramConnection txConn = null;
        try {

            txConn = (DatagramConnection) Connector.open("radiogram://broadcast:" + 100);
            Datagram dg = txConn.newDatagram(txConn.getMaximumLength());
            //send an update to the

            dg.writeByte(GENERIC);
            dg.writeByte(DISPLAY_SERVER_START);        // packet type
            txConn.send(dg);                             // broadcast it
        } catch (Exception ex) {
            if(DEBUG) System.out.println("Error sending display server startup message: " + ex.toString());
        //ex.printStackTrace();
        } finally {
            try {
                if (txConn != null) {
                    txConn.close();
                }
            } catch (IOException ex) {
                /* ignore */
            }
        }
    }


    private void announceClosing() {
        DatagramConnection txConn = null;
        try {

            txConn = (DatagramConnection) Connector.open("radiogram://broadcast:" + 100);
            Datagram dg = txConn.newDatagram(txConn.getMaximumLength());
            //send an update to the

            dg.writeByte(GENERIC);
            dg.writeByte(DISPLAY_SERVER_STOP);        // packet type
            txConn.send(dg);                             // broadcast it
        } catch (Exception ex) {
            if(DEBUG) System.out.println("Error sending display server startup message: " + ex.toString());
        //ex.printStackTrace();
        } finally {
            try {
                if (txConn != null) {
                    txConn.close();
                }
            } catch (IOException ex) {
                /* ignore */
            }
        }

    }

    private void receive(int[] payLoad) {

        int scale = 1;

        //if(DEBUG) System.out.println("Listener: Data payload = " + payLoad.length);
        //the data packet is 10 times of the same information.

        for (int i = 0; i < payLoad.length; i = i + (payLoad.length / 10)) {
            packetcnt++;
            //if(DEBUG) System.out.println("Listener - packet count "+ packetcnt+", myindex "+ myIndex);
            for (int j = 0; j < payLoad.length / 10; j++) {
                Byte whichG = (Byte)sensorFormat_Spot.get(j);
                //if(DEBUG) if(DEBUG) System.out.println("Listner - which G "+whichG.intValue());
                double x  = ((payLoad[i+j]) - zeroOffsets[scale][0]) / gains[scale][0];        // Convert to G's
//                double y  = (yValue - zeroOffsets[scale][1]) / gains[scale][1];
//                double z  = (zValue - zeroOffsets[scale][2]) / gains[scale][2];

//                double g = Math.sqrt(x*x + y*y + z*z);      // Square vector of the total Gs
                double g = Math.sqrt(x*x);
                
                myGUI.graphView[myIndex][whichG.intValue()].takeData(spotAddress, packetcnt, packetcnt, x, g, true);

            }
        }
    }


    //@Override
    public void run() {
        if(DEBUG) System.out.println("Listener Thread Started ...");
        hostLoop();
    }

    //private void mapSensors() {

      //  for (int i = 0; i < sensorFormat_Spot.size(); i++) {
         //   Integer tempInt = new Integer(sensorFormat_Gui.indexOf(sensorFormat_Spot.elementAt(i)));
            //if(DEBUG) System.out.println("Listener:mapSensors() tempInt " + tempInt.toString());
        //    sensorUpdates.add(tempInt);
       // }
   // }

    /**
     * Main receive loop. Receive a packet sent by remote SPOT and handle it.
     */
    private void hostLoop() {

        if(DEBUG) System.out.println("Listener: starts");

        findMyIndex();
        running = true;

        while (running) {
            //con = ClassDB.openConnection();     // Open Connection to Database
            waitForSpot();   // connect to a Spot
            if(DEBUG) System.out.println("Listener: After waitForSpot()");

            if (spotAddress != 0) {
                try {

                    myConn = (RadiogramConnection) Connector.open("radiogram://" + spotAddress + ":100");
                    rcvConn = (RadiogramConnection) Connector.open("radiogram://" + spotAddress + ":" + connected_port);
                    //conn.setTimeout(5000); //timeout every second
                    rcvConn.setTimeout(5000); //timeout every second
                    Radiogram rdg = (Radiogram) rcvConn.newDatagram(myConn.getMaximumLength());
                    xdg = (Radiogram) myConn.newDatagram(10); // we never send more than 1 or 2 bytes

                    connected = true;

                    if(DEBUG) System.out.println("Listener: spotaddress = " + spotAddress + " connected: " + connected);

                    //updateConnectionStatus(connected);
                    //index = 0;
                    //myIndex has the GUI panel to be updated.

                    //timeStampOffset = -1;
                    while (connected) {
                        if(DEBUG) System.out.println("Listener: inside connnected loop");
                        try {
                            if(DEBUG) System.out.println("Listener: inside connnected loop and trying to receive");
                            rcvConn.receive(rdg);
                            //datag = rdg;
                            if(DEBUG) System.out.println("Listener: now recieving commands from spot");
                        } catch (TimeoutException tout) {
                            if(DEBUG) System.out.println("Exception Occured!!!!");
                            continue;
                        }

                        if(DEBUG) System.out.println("Listener: now in connected loop");
                        //loop to parse the recieved data
                        byte sensortype = rdg.readByte();
                        byte commandtype = rdg.readByte();
                        switch (commandtype) {
                            //need to handle data once its recieved.
                            case FINISH_CONFIG_REPLY: {
                                if(DEBUG) System.out.println("Config Complete waiting for user input: ");

                                if (sensortype == GENERIC) {
                                    //this should have a list of the sensor types.
                                    if(DEBUG) System.out.println("FINISH_CONFIGURATION_REPLY from " + spotAddress);
                                    //int k = rdg.readInt();
                                    sensorFormat_Spot = new Vector();
                                    byte k = rdg.readByte();

                                    if(DEBUG) System.out.println("Listener->hostloop() length " + k);

                                    for (byte i = 0; i < k; i++) {
                                        sensorFormat_Spot.add(new Byte(rdg.readByte()));
                                    }
                                    for (int i = 0; i < sensorFormat_Spot.size(); i++) {
                                        if(DEBUG) System.out.println("Listener :hostloop() sensorformat" + sensorFormat_Spot.elementAt(i));
                                    }
                                } else {
                                    if(DEBUG) System.out.println("Listener->NonGeneric,FINISH_CONFIG_REPLY");
                                }
                                getSensorList();
                                announceStarting();
                                break;
                            }
                            case MESSAGE_REPLY: {
                                if (sensortype == GENERIC) {
                                    String str = rdg.readUTF();
                                    if(DEBUG) System.out.println(str);

                                } else {
                                    if(DEBUG) System.out.println("Listener->NonGeneric,MESSAGE_REPLY");
                                }
                                break;
                            }
                            case DATA_REPLY: {
                                if(DEBUG) System.out.println("Listener: Data reply received!!");
                                int k = rdg.readInt();
                                if(DEBUG) System.out.println("Listener->hostloop(): datalength " + k);
                                int[] arr;
                                arr = new int[k];
                                for (int i = 0; i < k; i++) {
                                    arr[i] = rdg.readInt();
                                }
                                receive(arr);
                            //receive(sensorSamples);
                            }
                        }
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                    //if(DEBUG) System.out.println("Error communicating with remote Spot: " + ex.toString());
                    try {
                        //ClassDB.closeConnection(con);
                        connected = false;
                        // updateConnectionStatus(connected);
                        if (myConn != null) {
                            xdg.reset();
                            xdg.writeByte(DISPLAY_SERVER_STOP);        // packet type
                            myConn.send(xdg);                                // broadcast it
                            myConn.close();
                            myConn = null;
                        }
                    } catch (IOException ex1) { /* ignore */ }
                }
            }
        }
    //running is false..
    }

    /**
     * Send a request to the remote SPOT to report on radio signal strength.
     */
    //public void doPing() {
    //  sendCmd(PING_REQ);
    //}
    /**
     * Send a request to the remote SPOT to blink its LEDs.
     */
    //public void doBlink() {
    //  sendCmd(BLINK_LEDS_REQ);
    //}
    /**
     * Pause running. Also notify the remote SPOT that we are no longer listening to it.
     */
    public void doPause() {
        sendCmd(GENERIC, DISPLAY_SERVER_PAUSE);
    // running = false;
    }

    /** Stop the thread and send command to the spot that the server is exiting
     *
     */
    public void doStop() {
        sendCmd(GENERIC, DISPLAY_SERVER_STOP);
        running = false;
        //ynchronized(mySPOTLIST){
        mySPOTLIST.remove(this);
    // }
    }
}
